﻿//   Copyright 2013 Entertainment Merchants Association

//   Licensed under the Apache License, Version 2.0 (the "License");
//   you may not use this file except in compliance with the License.
//   You may obtain a copy of the License at

//       http://www.apache.org/licenses/LICENSE-2.0

//   Unless required by applicable law or agreed to in writing, software
//   distributed under the License is distributed on an "AS IS" BASIS,
//   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//   See the License for the specific language governing permissions and
//   limitations under the License.


using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using EmaMetadata;
using System.IO;
using System.Media;

namespace EmaEditor
{
    public partial class frmMain : Form
    {
        public EmaBatch _batch = new EmaBatch();
        public bool _isdirty = false;
        public string _filename = null;
        public Stack<string> _undo_buffer = new Stack<string>();
        public Stack<string> _redo_buffer = new Stack<string>();
        public string _current_state = null;

        public frmMain()
        {
            InitializeComponent();

            // Start with a fresh file
            NewFile();
        }

        #region Helpers
        /// <summary>
        /// Update the display with information from the currently loaded batch
        /// </summary>
        public void Rebind()
        {
            if (_filename == null) {
                tsActivity.Text = String.Format("New file, {0} products", _batch.Products.Count);
            } else {
                tsActivity.Text = String.Format("Loaded file {0}, {1} products", Path.GetFileName(_filename), _batch.Products.Count);
            }

            // Generate a data table with relevant information about the products
            DataTable dt = new DataTable();
            dt.Columns.Add("GTIN");
            dt.Columns.Add("Platform");
            dt.Columns.Add("Title");
            dt.Columns.Add("Publisher");
            dt.Columns.Add("Developer");
            dt.Columns.Add("Genre");
            foreach (EmaProduct p in _batch.Products) {
                DataRow dr = dt.NewRow();
                EmaIdentity id = p.GetIdentity("org.gs1", "GTIN-14");
                dr["GTIN"] = id == null ? "" : id.Value;
                dr["Platform"] = p.Platform == null ? "" : p.Platform.FactName;
                EmaTitle t = p.GetTitle(null, "Complete");
                dr["Title"] = t == null ? "" : t.Value;
                dr["Publisher"] = p.PrimaryPublisher;
                dr["Developer"] = p.PrimaryDeveloper;
                dr["Genre"] = p.Genre == null ? "" : p.Genre.FactName;
                dt.Rows.Add(dr);
            }
            grdProducts.AutoGenerateColumns = true;
            grdProducts.DataSource = dt;
        }
        #endregion

        #region Menu Items
        private void frmEditor_FormClosing(object sender, FormClosingEventArgs e)
        {
            e.Cancel = !ConfirmSave();
        }

        private void editToolStripMenuItem1_Click(object sender, EventArgs e)
        {
            EditSelectedProduct();
        }

        private void redoToolStripMenuItem_Click(object sender, EventArgs e)
        {
            // Beep if you can't do this anymore
            if (_redo_buffer.Count == 0) {
                SystemSounds.Beep.Play();
                return;
            }

            // Push current state onto the "redo" buffer
            if (_current_state != null) {
                _undo_buffer.Push(_current_state);
            }

            // Reload the last state of the batch from the stack
            _current_state = _redo_buffer.Pop();
            _batch = Factory.LoadBatchFromString(_current_state);
            Rebind();
            ResetMenuStates();
        }

        private void undoToolStripMenuItem_Click(object sender, EventArgs e)
        {
            // Beep if you can't do this anymore
            if (_undo_buffer.Count == 0) {
                SystemSounds.Beep.Play();
                return;
            }

            // Push current state onto the "redo" buffer
            if (_current_state != null) {
                _redo_buffer.Push(_current_state);
            }

            // Reload the last state of the batch from the stack
            _current_state = _undo_buffer.Pop();
            _batch = Factory.LoadBatchFromString(_current_state);
            Rebind();
            ResetMenuStates();

            // If we are now at an "empty undo buffer" state, we are by definition back to "file not changed".
            if (_undo_buffer.Count == 0) {
                _isdirty = false;
            }
        }

        private void duplicateProductToolStripMenuItem_Click(object sender, EventArgs e)
        {
            // Make sure something is selected
            if (grdProducts.SelectedRows.Count > 0) {

                // Go through each product
                var batch = MakeNewBatchFromSelected();
                DuplicateProducts(batch.Products);
            }
        }

        private void cutToolStripMenuItem_Click(object sender, EventArgs e)
        {
            // Make the cursor flash so they know something has happened
            Cursor.Current = Cursors.WaitCursor;

            // Make sure something is selected
            if (grdProducts.SelectedRows.Count > 0) {

                // Go through each product and add it to a new batch object
                EmaBatch new_batch = MakeNewBatchFromSelected();

                // Send to clipboard
                Clipboard.SetText(Factory.SaveBatchToString(new_batch));

                // Remove all selected items, since they're on the clipboard now
                DeleteSelected();
            } else {
                SystemSounds.Beep.Play();
            }

            // Make the cursor flash so they know something has happened
            Cursor.Current = Cursors.Default;
        }

        private void copyToolStripMenuItem_Click(object sender, EventArgs e)
        {
            // Make the cursor flash so they know something has happened
            Cursor.Current = Cursors.WaitCursor;

            // Make sure something is selected
            if (grdProducts.SelectedRows.Count > 0) {

                // Go through each product and add it to a new batch object
                EmaBatch new_batch = new EmaBatch();
                for (int i = 0; i < grdProducts.SelectedRows.Count; i++) {
                    EmaProduct p = _batch.Products[grdProducts.SelectedRows[i].Index];
                    new_batch.AddProduct(p);
                }

                // Send to clipboard
                Clipboard.SetText(Factory.SaveBatchToString(new_batch));
            }

            // Make the cursor flash so they know something has happened
            Cursor.Current = Cursors.Default;
        }

        private void pasteToolStripMenuItem_Click(object sender, EventArgs e)
        {
            // Make the cursor flash so they know something has happened
            Cursor.Current = Cursors.WaitCursor;

            // Retrieve the clipboard
            try {
                EmaBatch new_batch = Factory.LoadBatchFromString(Clipboard.GetText());
                AppendBatch(new_batch);

            } catch (Exception ex) {
                System.Diagnostics.Debug.WriteLine(ex.ToString());
                MessageBox.Show("The clipboard does not contain data in the EMA Metadata format.", "Cannot Paste", MessageBoxButtons.OK);
            }

            // Make the cursor flash so they know something has happened
            Cursor.Current = Cursors.Default;
        }

        private void propertiesToolStripMenuItem_Click(object sender, EventArgs e)
        {
            frmProperties frm = new frmProperties(_batch);
            if (frm.ShowDialog() == System.Windows.Forms.DialogResult.OK) {
                SetDirty(true);
            }
        }

        private void appendFileToolStripMenuItem_Click(object sender, EventArgs e)
        {
            OpenFileDialog ofd = new OpenFileDialog();
            ofd.Title = "Select an EMA Metadata format file to append";
            ofd.DefaultExt = ".xml";
            ofd.Filter = "XML files (*.xml)|*.xml|All Files (*.*)|*.*";
            if (ofd.ShowDialog() == System.Windows.Forms.DialogResult.OK) {
                try {

                    // Read in the new batch
                    EmaBatch append_batch = Factory.LoadBatch(ofd.FileName);
                    AppendBatch(append_batch);

                } catch (Exception ex) {
                    MessageBox.Show(ex.Message, "Exception loading EMA batch");
                }
            }
        }

        /// <summary>
        /// Open a new file
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void openToolStripMenuItem_Click(object sender, EventArgs e)
        {
            if (ConfirmSave()) {
                OpenFileDialog ofd = new OpenFileDialog();
                ofd.Title = "Open an EMA Metadata format file";
                ofd.DefaultExt = ".xml";
                ofd.Filter = "XML files (*.xml)|*.xml|All Files (*.*)|*.*";
                if (ofd.ShowDialog() == System.Windows.Forms.DialogResult.OK) {
                    try {
                        LoadFile(ofd.FileName);
                    } catch (Exception ex) {
                        MessageBox.Show(ex.Message, "Exception loading EMA batch");
                    }
                }
            }
        }

        /// <summary>
        /// Save the current file
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void saveToolStripMenuItem_Click(object sender, EventArgs e)
        {
            if (String.IsNullOrEmpty(_filename)) {
                saveAsToolStripMenuItem_Click(sender, e);
            } else {
                SaveFile(_filename);
            }
        }

        /// <summary>
        /// Save the file somewhere else
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void saveAsToolStripMenuItem_Click(object sender, EventArgs e)
        {
            SaveFileDialog sfd = new SaveFileDialog();
            sfd.Title = "Save Ema Metadata File";
            sfd.DefaultExt = ".xml";
            sfd.Filter = "XML files (*.xml)|*.xml|All Files (*.*)|*.*";
            if (sfd.ShowDialog() == System.Windows.Forms.DialogResult.OK) {
                try {
                    SaveFile(sfd.FileName);
                } catch (Exception ex) {
                    MessageBox.Show(ex.Message, "Exception saving EMA batch");
                }
            }
        }

        /// <summary>
        /// Exit the program
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void exitToolStripMenuItem_Click(object sender, EventArgs e)
        {
            if (ConfirmSave()) {
                this.Close();
            }
        }

        private void newToolStripMenuItem_Click(object sender, EventArgs e)
        {
            if (ConfirmSave()) {
                NewFile();
            }
        }

        private void eMAMetadataFormatReferenceToolStripMenuItem_Click(object sender, EventArgs e)
        {
            System.Diagnostics.Process.Start("http://entmerch.org/programsinitiatives/the-ema-metadata-structure");
        }

        private void aboutToolStripMenuItem_Click(object sender, EventArgs e)
        {
            frmAbout f = new frmAbout();
            f.ShowDialog();
        }

        private void frmEditor_Activated(object sender, EventArgs e)
        {
            ResetMenuStates();
        }

        private void grdProducts_SelectionChanged(object sender, EventArgs e)
        {
            ResetMenuStates();
        }

        private void deleteToolStripMenuItem_Click(object sender, EventArgs e)
        {
            // Make sure something is selected
            if (grdProducts.SelectedRows.Count > 0) {

                // Ask the user to confirm their action
                if (MessageBox.Show(String.Format("Do you wish to delete {0} product(s)?", grdProducts.SelectedRows.Count),
                    "Confirm Delete", MessageBoxButtons.YesNoCancel) == System.Windows.Forms.DialogResult.Yes) {
                    EmaBatch b = MakeNewBatchFromSelected();

                    // Okay, the user confirmed, let's delete them
                    DeleteSelected();
                }
            }
        }
        #endregion

        #region Grid actions
        /// <summary>
        /// If user clicks on the icon
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void grdProducts_CellClick(object sender, DataGridViewCellEventArgs e)
        {
            if (e.ColumnIndex == 0) {

                // Select the product
                if (e.RowIndex >= 0) {

                    // Edit the product
                    EditSelectedProduct();
                }
            } else {
                //grdProducts.BeginEdit(false);
            }
        }

        private void grdProducts_KeyDown(object sender, KeyEventArgs e)
        {
            if (grdProducts.SelectedRows.Count > 0) {
                //grdProducts.BeginEdit(false);
            }
        }

        /// <summary>
        /// User wants to edit a grid item
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void grdProducts_CellDoubleClick(object sender, DataGridViewCellEventArgs e)
        {
            //// Select the product
            //if (e.RowIndex >= 0) {

            //    // Edit the product
            //    EditSelectedProduct();
            //}
        }

        /// <summary>
        /// User wants to add a new product
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnNew_Click(object sender, EventArgs e)
        {
            frmProduct dlg = new frmProduct(_batch, new EmaProduct(), this);
            if (dlg.ShowDialog() == System.Windows.Forms.DialogResult.OK) {
                SetDirty(true);
                Rebind();
            }
        }

        private void grdProducts_MouseDown(object sender, MouseEventArgs e)
        {
            if (e.Button == System.Windows.Forms.MouseButtons.Right) {
                var ht = grdProducts.HitTest(e.X, e.Y);

                // If user clicked on the top row
                if (ht.RowIndex < 0) {
                    //frmColumnSelector dlg = new frmColumnSelector();
                    //dlg.ShowDialog(this);
                    //return;
                }

                // Check where the user clicked and activate that row
                if (ht.RowIndex >= 0) {

                    // If the Ctrl key is held down, invert the selection add to the selection
                    if ((ModifierKeys & Keys.Control) > 0) {
                        grdProducts.Rows[ht.RowIndex].Selected = !grdProducts.Rows[ht.RowIndex].Selected;

                    // No modifier keys - replace the selection completely
                    } else {

                        // If the product isn't already selected, clear the selection and select only this item
                        if (!grdProducts.Rows[ht.RowIndex].Selected) {

                            for (int i = 0; i < grdProducts.Rows.Count; i++) {
                                grdProducts.Rows[i].Selected = (i == ht.RowIndex);
                            }
                        }
                    }
                }

                // Show the context menu, if any rows are still selected
                if (grdProducts.SelectedRows.Count > 0) {
                    mnuGridContext.Show(grdProducts, e.Location);
                }
            }
        }
        #endregion

        #region Controllers
        /// <summary>
        /// Detect the data that's on the clipboard and set controls active as appropriate
        /// </summary>
        private void ResetMenuStates()
        {
            // You can cut, copy, paste, delete, or duplicate if at least one row is selected
            cutToolStripMenuItem.Enabled = (grdProducts.SelectedRows.Count > 0);
            cutToolStripMenuItem1.Enabled = (grdProducts.SelectedRows.Count > 0);
            copyToolStripMenuItem.Enabled = (grdProducts.SelectedRows.Count > 0);
            copyToolStripMenuItem1.Enabled = (grdProducts.SelectedRows.Count > 0);
            deleteToolStripMenuItem.Enabled = (grdProducts.SelectedRows.Count > 0);
            deleteToolStripMenuItem2.Enabled = (grdProducts.SelectedRows.Count > 0);
            duplicateProductToolStripMenuItem.Enabled = (grdProducts.SelectedRows.Count > 0);
            duplicateToolStripMenuItem.Enabled = (grdProducts.SelectedRows.Count > 0);

            // You can edit if you have one and only one product selected
            editToolStripMenuItem1.Enabled = (grdProducts.SelectedRows.Count == 1);

            // You can paste if the clipboard has text (no guarantee it will contain valid XML, though!)
            pasteToolStripMenuItem.Enabled = Clipboard.ContainsText();
            pasteToolStripMenuItem1.Enabled = Clipboard.ContainsText();

            // You can undo if the undo buffer is not empty
            undoToolStripMenuItem.Enabled = _undo_buffer.Count > 0;
            redoToolStripMenuItem.Enabled = _redo_buffer.Count > 0;
        }

        /// <summary>
        /// Duplicate all items in a selected list of products
        /// </summary>
        /// <param name="list"></param>
        private void DuplicateProducts(List<EmaProduct> list)
        {
            foreach (EmaProduct p in list) {
                EmaProduct dup = Factory.StringToProduct(Factory.ProductToString(p));
                dup.SetTitle(Factory.ENTMERCH_NAMESPACE, "Complete", Factory.TITLE_COMPLETE_ID, dup.GetTitle(Factory.ENTMERCH_NAMESPACE, "Complete").Value + " (Copy)");
                _batch.AddProduct(dup);
            }

            // Rebind
            SetDirty(true);
            Rebind();
        }

        /// <summary>
        /// Delete all selected objects
        /// </summary>
        private void DeleteSelected()
        {
            // Retrieve selected products and remove them
            foreach (EmaProduct p in MakeNewBatchFromSelected().Products) {
                _batch.Products.Remove(p);
            }

            // Reset display
            SetDirty(true);
            Rebind();
        }

        /// <summary>
        /// Create a new EMABatch object containing all those products that are currently selected
        /// </summary>
        /// <returns></returns>
        private EmaBatch MakeNewBatchFromSelected()
        {
            EmaBatch new_batch = new EmaBatch();
            for (int i = 0; i < grdProducts.SelectedRows.Count; i++) {
                EmaProduct p = _batch.Products[grdProducts.SelectedRows[i].Index];
                new_batch.AddProduct(p);
            }
            return new_batch;
        }

        /// <summary>
        /// Take a new batch and append it to the existing batch
        /// </summary>
        /// <param name="append_batch"></param>
        private void AppendBatch(EmaBatch append_batch)
        {
            // Append all its products to our own
            _batch.Products.AddRange(append_batch.Products);
            MessageBox.Show(String.Format("Added {0} products.", append_batch.Products.Count), "Append Successful", MessageBoxButtons.OK);
            SetDirty(true);
            Rebind();
        }

        /// <summary>
        /// If the file has changed, prompt the user whether they would like to save their changes.
        /// </summary>
        /// <returns></returns>
        private bool ConfirmSave()
        {
            if (_isdirty) {
                DialogResult dr = MessageBox.Show("Your file has changed.\r\n\r\nDo you wish to save your changes?", "Confirm Save", MessageBoxButtons.YesNoCancel);

                // If the user cancelled, don't do anything
                if (dr == System.Windows.Forms.DialogResult.Cancel) {
                    return false;
                }

                // If the user selected "yes", save the file before proceeeding
                if (dr == System.Windows.Forms.DialogResult.Yes) {
                    saveToolStripMenuItem_Click(null, EventArgs.Empty);
                }
            }

            // Okay to proceed - nothing's changed
            return true;
        }

        /// <summary>
        /// Something has happened to the batch, so keep track of a change.
        /// </summary>
        /// <param name="dirty"></param>
        public void SetDirty(bool dirty)
        {
            // Track the dirty statuses
            if (dirty) {
                saveToolStripMenuItem.Enabled = true;
                _isdirty = true;
            } else {
                saveToolStripMenuItem.Enabled = false;
                _isdirty = false;
            }

            // If we are setting the dirty flag to "false", the undo buffer resets
            if (!_isdirty) {
                _undo_buffer.Clear();
                _redo_buffer.Clear();
                _current_state = null;
            }

            // Record our current state information
            if (_current_state != null) {
                _undo_buffer.Push(_current_state);
            }
            _current_state = Factory.SaveBatchToString(_batch);
        }

        /// <summary>
        /// Save the current batch to this filename
        /// </summary>
        /// <param name="save_filename"></param>
        private void SaveFile(string save_filename)
        {
            try {
                _batch.LastUpdateDate = DateTime.Now;
                Factory.SaveBatch(_batch, save_filename);
                _filename = save_filename;
                SetDirty(false);
                Rebind();
            } catch (Exception ex) {
                MessageBox.Show(ex.Message, "Exception saving EMA batch");
            }
        }

        /// <summary>
        /// Load the batch from a new filename
        /// </summary>
        /// <param name="load_filename"></param>
        private void LoadFile(string load_filename)
        {
            try {
                _batch = Factory.LoadBatch(load_filename);
                _filename = load_filename;
                SetDirty(false);
                Rebind();
            } catch (Exception ex) {
                MessageBox.Show(ex.Message, "Exception loading EMA batch");
            }
        }

        /// <summary>
        /// Start over with a blank batch
        /// </summary>
        private void NewFile()
        {
            _batch = new EmaBatch();
            _filename = null;
            Rebind();
            SetDirty(false);
        }

        /// <summary>
        /// Edit the selected product
        /// </summary>
        private void EditSelectedProduct()
        {
            EmaBatch sel = MakeNewBatchFromSelected();
            if (sel.Products.Count == 1) {
                frmProduct dlg = new frmProduct(_batch, sel.Products[0], this);
                if (dlg.ShowDialog() == System.Windows.Forms.DialogResult.OK) {
                    SetDirty(true);
                    Rebind();
                }

            // Beep to remind the user nothing is selected, or too many items have been selected
            } else {
                SystemSounds.Beep.Play();
            }
        }
        #endregion

        #region Duplicate product for new platform
        private void mnuGridContext_Opening(object sender, CancelEventArgs e)
        {
            List<ToolStripItem> items = new List<ToolStripItem>();
 
            // Add some placeholders
            ToolStripMenuItem tsmi = new ToolStripMenuItem();
            tsmi.Text = "Same platform";
            tsmi.Tag = null;
            tsmi.Click += new EventHandler(DuplicateForPlatform_Click);
            items.Add(tsmi);
            items.Add(new ToolStripSeparator());

            // Go through all products, in name order
            var list = from i in Factory.GetItems(Factory.TABLE_PLATFORMS) orderby i.FactName select i;
            foreach (var i in list) {
                tsmi = new ToolStripMenuItem();
                tsmi.Text = i.FactName;
                tsmi.Tag = i;
                tsmi.Click += new EventHandler(DuplicateForPlatform_Click);
                items.Add(tsmi);
            }

            // Here is where Fitwee will automatically generate the list of all products in the menu
            this.duplicateToolStripMenuItem.DropDownItems.Clear();
            this.duplicateToolStripMenuItem.DropDownItems.AddRange(items.ToArray());
        }

        void DuplicateForPlatform_Click(object sender, EventArgs e)
        {
            var tsmi = sender as ToolStripMenuItem;
            var item = tsmi.Tag as LookupItem;
            foreach (EmaProduct p in MakeNewBatchFromSelected().Products) {
                EmaProduct dup = Factory.StringToProduct(Factory.ProductToString(p));
                if (item != null) {
                    dup.Platform = item.MakeFact();
                } else {
                    dup.SetTitle(Factory.ENTMERCH_NAMESPACE, "Complete", Factory.TITLE_COMPLETE_ID, dup.GetTitle(Factory.ENTMERCH_NAMESPACE, "Complete").Value + " (Copy)");
                }
                _batch.AddProduct(dup);
            }
            SetDirty(true);
            Rebind();
        }
        #endregion

        private void dataEntryHelpToolStripMenuItem_Click(object sender, EventArgs e)
        {
            System.Diagnostics.Process.Start("https://code.google.com/p/ema-metadata/wiki/CoreDataItems");
        }

        private void grdProducts_CellEndEdit(object sender, DataGridViewCellEventArgs e)
        {
            // Retrieve the currently selected product
            var p = _batch.Products[e.RowIndex];

            // Modify the values, dependent on column name
            if (grdProducts.Columns[e.ColumnIndex].Name == "Title") {
                string newtitle = grdProducts.Rows[e.RowIndex].Cells[e.ColumnIndex].Value.ToString();
                p.SetTitle(Factory.ENTMERCH_NAMESPACE, "Complete", Factory.TITLE_COMPLETE_ID, newtitle);
            }

            // Update the values
            _batch.Products.RemoveAt(e.RowIndex);
            _batch.Products.Insert(e.RowIndex, p);
            SetDirty(true);
        }

        private void quickstartGuideToolStripMenuItem_Click(object sender, EventArgs e)
        {
            var path = System.IO.Path.GetDirectoryName(
            System.Reflection.Assembly.GetExecutingAssembly().GetName().CodeBase);

            System.Diagnostics.Process.Start(Path.Combine(path, @"Quickstart Guide.pdf"));
        }
    }
}
